

#include <map>
#include <mutex>
#include <tuple>
#include <type_traits>

class null_para { };
template <typename Sig, class F>
class memoize_helper;

template <typename Result, typename... Args, class F>
class memoize_helper<Result(Args...), F> {
private:
    using function_type = F;
    using args_tuple_type = std::tuple<std::decay_t<Args>...>;
    function_type f;
    mutable std::map<args_tuple_type, Result> m_cache;
    mutable std::recursive_mutex m_cache_mutex;

public:
    template <typename Function>
    memoize_helper(Function&& f, null_para)
        : f(f) {};

    memoize_helper(memoize_helper& other)
        : f(other.f)
    {
    }

    template <typename... InnerArgs>
    Result operator()(InnerArgs&&... args) const
    {
        std::unique_lock<std::recursive_mutex> lock(m_cache_mutex);
        const auto args_tuple = std::make_tuple(args...);
        const auto cached = m_cache.find(args_tuple);
        if (cached != m_cache.end()) {
            return cached->second;
        } else {
            auto&& result = f(*this, std::forward<InnerArgs...>(args)...);
            m_cache[args_tuple] = result;
            return result;
        }
    }
};

template <typename Sig, typename F>
memoize_helper<Sig, std::decay_t<F>>
make_memoized_r(F&& f)
{
    return { std::forward<F>(f), null_para() };
}